package http

import (
	"context"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"syscall"
	"time"
)

const (
	gracefulEnvironKey   = "HTTP_GRACE_RELOAD"
	gracefulEnvironValue = "1"
	gracefulListenerFD   = 3
)

//Server HTTP server that supported graceful shutdown or restart
type Server struct {
	HTTPServer   *http.Server
	listener     net.Listener
	isGraceful   bool
	signalChan   chan os.Signal
	shutdownChan chan bool
}

// NewServer ..
func NewServer(httpSrv *http.Server) *Server {
	var isGraceful bool
	if os.Getenv(gracefulEnvironKey) == gracefulEnvironValue {
		isGraceful = true
	}
	return &Server{
		HTTPServer:   httpSrv,
		isGraceful:   isGraceful,
		signalChan:   make(chan os.Signal),
		shutdownChan: make(chan bool),
	}
}

//ListenAndServe ..
func (srv *Server) ListenAndServe() error {
	addr := srv.HTTPServer.Addr
	if addr == "" {
		addr = ":http"
	}
	err := srv.setListener(addr)
	if err != nil {
		return err
	}
	return srv.serve()
}

//ListenAndServeTLS ..
func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error {
	addr := srv.HTTPServer.Addr
	if addr == "" {
		addr = ":http"
	}
	err := srv.setListener(addr)
	if err != nil {
		return err
	}
	return srv.serveTLS(certFile, keyFile)
}

func (srv *Server) setListener(addr string) (err error) {
	var ln net.Listener
	if srv.isGraceful {
		file := os.NewFile(gracefulListenerFD, "")
		ln, err = net.FileListener(file)
		if err != nil {
			return fmt.Errorf("net.FileListener error: %v", err)
		}
	} else {
		ln, err = net.Listen("tcp", addr)
		if err != nil {
			return fmt.Errorf("net.Listen error: %v", err)
		}
	}
	srv.listener = ln
	return nil
}

//Serve ..
func (srv *Server) serve() error {
	go srv.handleSignals()
	err := srv.HTTPServer.Serve(tcpKeepAliveListener{srv.listener.(*net.TCPListener)})

	srv.logf("waiting for connections closed.")
	<-srv.shutdownChan
	srv.logf("all connections closed.")

	return err
}

//serveTLS ..
func (srv *Server) serveTLS(certFile, keyFile string) error {
	go srv.handleSignals()
	err := srv.HTTPServer.ServeTLS(tcpKeepAliveListener{srv.listener.(*net.TCPListener)}, certFile, keyFile)

	srv.logf("waiting for connections closed.")
	<-srv.shutdownChan
	srv.logf("all connections closed.")

	return err
}

func (srv *Server) logf(format string, args ...interface{}) {
	pids := strconv.Itoa(os.Getpid())
	format = "[pid " + pids + "] " + format

	if srv.HTTPServer.ErrorLog != nil {
		srv.HTTPServer.ErrorLog.Printf(format, args...)
	} else {
		log.Printf(format, args...)
	}
}

//处理信号
func (srv *Server) handleSignals() {
	var sig os.Signal

	signal.Notify(
		srv.signalChan,
		syscall.SIGTERM,
		syscall.SIGUSR2,
		syscall.SIGUSR1,
	)

	for {
		sig = <-srv.signalChan
		switch sig {
		case syscall.SIGTERM:
			srv.logf("received SIGTERM, graceful shutting down HTTP server.")
			srv.shutdownHTTPServer()
		case syscall.SIGUSR2, syscall.SIGUSR1:
			srv.logf("received SIGUSR2, graceful restarting HTTP server.")

			if pid, err := srv.forkNewProcess(); err != nil {
				srv.logf("fork new process failed: %v, continue serving.", err)
			} else {
				srv.logf("fork new process successed, the new pid is %d.", pid)
				srv.shutdownHTTPServer()
			}
		default:
		}
	}
}

//关闭http监听服务
func (srv *Server) shutdownHTTPServer() {
	if err := srv.HTTPServer.Shutdown(context.Background()); err != nil {
		srv.logf("HTTP server shutdown error: %v", err)
	} else {
		srv.logf("HTTP server shutdown success.")
		srv.shutdownChan <- true
	}
}

// fork new process to handle HTTP Connection
func (srv *Server) forkNewProcess() (uintptr, error) {
	listenerFd, err := srv.getTCPListenerFd()
	if err != nil {
		return 0, fmt.Errorf("failed to get socket file descriptor: %v", err)
	}

	// set graceful restart env flag
	envs := make([]string, 100)
	os.Setenv(gracefulEnvironKey, gracefulEnvironValue)
	for _, value := range os.Environ() {
		envs = append(envs, value)
	}

	procAttr := &syscall.ProcAttr{
		Env:   envs,
		Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listenerFd},
	}

	pid, err := syscall.ForkExec(os.Args[0], os.Args, procAttr)
	if err != nil {
		return 0, fmt.Errorf("failed to forkexec: %v", err)
	}

	return uintptr(pid), nil
}

func (srv *Server) getTCPListenerFd() (u uintptr, err error) {
	file, err := srv.listener.(*net.TCPListener).File()
	if err != nil {
		return
	}
	return file.Fd(), nil
}

// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
type tcpKeepAliveListener struct {
	*net.TCPListener
}

func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return nil, err
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(3 * time.Minute)
	return tc, nil
}
